#include "parafluidsph.h"
#include "parafluidsphdensity.h"
#include "parafluidsphforce.h"
#include "parafluidsphposition.h"

#include "mpicalculator.h"

#include <QtDebug>
#include <QtGlobal>
#include <QByteArray>

using namespace ParaFluid;

SPH::~SPH()
{
	/* does nothing */
}

SPH::SPH(unsigned int buckets, float smoothingDistance, float timeStep, const Vector & gravity, const Box & constraints)
	: atomVoxelizer(buckets, smoothingDistance)
	, _currentTimeStep(timeStep)
	, _currentGravity(gravity)
	, _currentConstraints(constraints)
	, _newSmoothingDistance(smoothingDistance)
	, _newTimeStep(timeStep)
	, _newGravity(gravity)
	, _newConstraints(constraints)
	, _lastTimeStep(timeStep)
	, _clear(false)
{
	/* does nothing */
}

void SPH::serialize(QDataStream & stream) const
{
	stream << atomVoxelizer;
	stream << _currentTimeStep;
	stream << _currentGravity;
	stream << _currentConstraints;
	stream << _newSmoothingDistance;
	stream << _newTimeStep;
	stream << _newGravity;
	stream << _newConstraints;
	stream << _lastTimeStep;
	stream << _clear;
}

void SPH::unserialize(QDataStream & stream)
{
	stream >> atomVoxelizer;
	stream >> _currentTimeStep;
	stream >> _currentGravity;
	stream >> _currentConstraints;
	stream >> _newSmoothingDistance;
	stream >> _newTimeStep;
	stream >> _newGravity;
	stream >> _newConstraints;
	stream >> _lastTimeStep;
	stream >> _clear;
}

float SPH::smoothingDistance() const
{
	return _newSmoothingDistance;
}

float SPH::timeStep() const
{
	return _newTimeStep;
}

float SPH::lastTimeStep() const
{
	return _lastTimeStep;
}

const Vector & SPH::gravity() const
{
	return _newGravity;
}

const Box & SPH::constraints() const
{
	return _newConstraints;
}

void SPH::setSmoothingDistance(float value)
{
	_newSmoothingDistance = value;
}

void SPH::setTimeStep(float value)
{
	_newTimeStep = value;
}

void SPH::setGravity(const Vector & value)
{
	_newGravity = value;
}

void SPH::setConstraints(const Box & value)
{
	_newConstraints = value;
}

void SPH::clear()
{
	_clear = true;
}

void SPH::step()
{
	updateMembers();
	
	DensityUpdater().updateDensity(*this);
	ForceUpdater().updateForces(*this);
	PositionUpdater().updatePosition(*this);
	
	atomVoxelizer.rehash();
}

void SPH::parallelStepMaster()
{
	updateMembers();
	
	int numAtoms = 100;
	int numAtomsParNoeud = numAtoms / 2;
	
	// For all three Steps
	for(int step = 0; step < 3; ++step)
	{
		// Send SPH and Index
		for(int i = 1; i < 3; ++i)
		{
			{
				QByteArray data;
				QDataStream stream(&data, QIODevice::WriteOnly);
				stream << *this;
			}
			MPI_sendSPH(i, data.data(), data.size());
			MPI_sendIndex(i, (i - 1) * numAtomsParNoeud, (i * numAtomsParNoeud) - 1);
		}
		
		// Receive Index and Result
		for(int i = 1; i < 3; ++i)
		{
			int indexFirst = 0;
			int indexLast = 0;
			ParaFluid::MPI_receiveIndex(i, indexFirst, indexLast);
			
			{
				char * buffer;
				int size;
				ParaFluid::MPI_receiveResult(buffer, size);
				QByteArray data(buffer, size);
				QDataStream stream(&data, QIODevice::ReadOnly);
				
				parallelUnserializeAtom(stream, indexFirst, indexLast);
			}
		}
	}
	
	atomVoxelizer.rehash();
}

void SPH::parallelStepSlave1(QDataStream & result, int indexFirst, int indexLast)
{
	DensityUpdater().updateDensityParallel(*this, indexFirst, indexLast);
	
	parallelSerializeAtom(result, indexFirst, indexLast);
}

void SPH::parallelStepSlave2(QDataStream & result, int indexFirst, int indexLast)
{
	ForceUpdater().updateForcesParallel(*this, indexFirst, indexLast);
	
	parallelSerializeAtom(result, indexFirst, indexLast);
}

void SPH::parallelStepSlave3(QDataStream & result, int indexFirst, int indexLast)
{
	PositionUpdater().updatePositionParallel(*this, indexFirst, indexLast);
	
	parallelSerializeAtom(result, indexFirst, indexLast);
}

void SPH::parallelSerializeAtom(QDataStream & stream, int indexFirst, int indexLast)
{
	
	class Serializer
		: public AtomVoxelizer::ReadWriteVisitor
	{
		
	private:
		
		QDataStream * _stream;
		
	public:
		
		Serializer(QDataStream * stream) : _stream(stream) { /* does nothing */ }
		
		void visit(Atom & atom)
		{
			*_stream << atom;
		}
		
	} visitor(&stream);
	
	atomVoxelizer.acceptVisitor(visitor, indexFirst, indexLast);
	
}

void SPH::parallelUnserializeAtom(QDataStream & stream, int indexFirst, int indexLast)
{
	
	class Unserializer
		: public AtomVoxelizer::ReadWriteVisitor
	{
		
	private:
		
		QDataStream * _stream;
		
	public:
		
		Unserializer(QDataStream * stream) : _stream(stream) { /* does nothing */ }
		
		void visit(Atom & atom)
		{
			*_stream >> atom;
		}
		
	} visitor(&stream);
	
	atomVoxelizer.acceptVisitor(visitor, indexFirst, indexLast);
	
}

void SPH::updateMembers()
{
	_lastTimeStep = _currentTimeStep;
	_currentTimeStep = _newTimeStep;
	_currentGravity = _newGravity;
	_currentConstraints = _newConstraints;
	atomVoxelizer.setSmoothingDistance(_newSmoothingDistance);
	if(_clear)
	{
		_clear = false;
		atomVoxelizer.clear();
	}
}

QDataStream & operator<< (QDataStream & stream, const ParaFluid::SPH & sph)
{
	sph.serialize(stream);
	return stream;
}

QDataStream & operator>> (QDataStream & stream, ParaFluid::SPH & sph)
{
	sph.unserialize(stream);
	return stream;
}
